/*
 * Sync.cpp
 *
 *  Created on: 19.01.2012
 *      Author: mario
 */

#include "Sync.h"
#include "../UI_multiplexer.h"

string Sync::COMPOSE_TAG(string base_tag, UserPtr user)
{
    return base_tag + " " + user->get_name();
}


Sync::Sync()  :
    tag(),
    partner(),
    mine_path(),
    their_path(),
    sync_type(),
    receiver_accepted(false),
    direct_accept(true),
    outgoing_transfers_enabled(false),
    incoming_transfers_enabled(false),
    update_notification_pending(false),
    update_listener(NULL),
    sending(false),
	sleeping(true)
{
}

Sync::~Sync()
{
    // TODO Auto-generated destructor stub
}

UserPtr Sync::get_partner() const
{
    return partner;
}

string Sync::get_tag() const
{
    return tag;
}

void Sync::set_partner(UserPtr partner)
{
    this->partner = partner;
}

void Sync::set_tag(string tag)
{
    this->tag = tag;
}

path Sync::get_mine_path() const
{
    return mine_path;
}

// TODO Diese Funktion soll einen Path zurückgeben, den der Sync-Partner sehen darf.
// Wenn der eigene Pfad vertraulich bleiben soll (Normalfall), wird der eigene Pfad gehasht, sodass identische Pfade erkannt werden können, aber mehr nicht.
// TODO --> Wenn der Ordner zB ein share ist, sollte der richtige Name angezeigt sein, da dieser dann nichtmehr schützenswert ist. Im Gegenteil.
//
// TODO Ich nehm zum hashen jetzt einfach eine Funktion aus Ariba. Wenn wir mal Krypto haben, dann vlt lieber eine Funktion von da nehmen.
//
// TODO Salt mit eigenem Username? (Dann müssen wir aber Userdb einbinden..)
string Sync::get_mine_path_partner_version() const
{
    return Identifier::sha1(mine_path.string()).getAddress();
}


Sync::SyncType Sync::get_sync_type() const
{
    return sync_type;
}

Sync::Role Sync::get_role() const
{
    return role;
}

boost::filesystem::path Sync::get_their_path() const
{
    return their_path;
}


void Sync::set_mine_path(path minePath)
{
    this->mine_path = minePath;
}


void Sync::set_sane_name_from_path(path p)
{
    // extract filename from path
    path name = p.filename();

    /** sanity check **/
    // mine_path.filename() should not be "."
    while ( name == boost::filesystem::path(".") )
    {
        p.remove_filename();
        name = p.filename();
    }

    // just in case.. name may not be ".." --> use another stupid name instead
    if ( name == boost::filesystem::path("..") )
    {
        name = boost::filesystem::path("PARENT");
    }

    // no hidden top-folders (would throw security exception on the other side)
    while ( name.string()[0] == '.' )
    {
        string s = name.string();
        s.erase(s.begin());

        name = s;
    }

    // no empty names
    if ( name.empty() )
    {
        name = "NONAME";
    }


    //*  * set name *  *//
    this->name = name;
}


void Sync::set_name(path name)
{
    this->name = name;
}



void Sync::set_sync_type_and_role(Sync::SyncType syncType, Sync::Role role)
{
    // set
    this->sync_type = syncType;
    this->role = role;


    // activate corresponding policy
    switch (role)
    {
        // send-only
        case Sync::SENDER:
        {
            outgoing_transfers_enabled = true;
            incoming_transfers_enabled = false;

            break;
        }
        // receive-only
        case Sync::RECEIVER:
        {
            outgoing_transfers_enabled = false;
            incoming_transfers_enabled = true;

            break;
        }

        // unknown -> be careful ;-)
        default:
        {
            outgoing_transfers_enabled = false;
            incoming_transfers_enabled = false;

            break;
        }
    }

}

void Sync::set_sync_type_and_role_inverted(Sync::SyncType syncType, Sync::Role role)
{
    /** inverter **/
    switch (syncType)
    {
        /** PUSH & PULL SYNCs **/
        case Sync::PUSH_SYNC:
        case Sync::PULL_SYNC:
        {
            /* switch roles */
            switch (role)
            {
                case Sync::SENDER:
                {
                    role = Sync::RECEIVER;

                    break;
                }
                case Sync::RECEIVER:
                {
                    role = Sync::SENDER;

                    break;
                }
                default:
                    break;
            }

            break;
        }

        default:
        {
            break;
        }
    }


    // * call normal setter *
    set_sync_type_and_role(syncType, role);
}


void Sync::set_their_path(boost::filesystem::path theirPath)
{
    their_path = theirPath;
}

bool Sync::get_receiver_accepted() const
{
    return receiver_accepted;
}

void Sync::set_receiver_accepted(bool receiverAccepted)
{
    receiver_accepted = receiverAccepted;
}


string Sync::get_composed_tag() const
{
    // ERR: should never happen (indicates programming errors):
    // Always set tag and partner before asking for the unique tag!
    if ( tag == "" || ! partner )
        throw runtime_error("SYNC: NO UNIQUE TAG!");


    return Sync::COMPOSE_TAG(tag, partner);
}


void Sync::scan()
{
	sleeping = false;

	// TODO Muss kein Directory sein
	path xpath("");
	base_file = SyncDirPtr(new Sync_Directory(xpath));
	base_file->set_base_path(mine_path);

    currently_scanned_file = base_file;
    base_file->scan(shared_from_this());
}


/* TODO
 * [X] Speicher frei machen
 * [ ] testen
 * [ ] was passiert, wenn noch ein scan läuft, während partner offline geht?
 * [ ] was passiert, wenn nach dem wieder online gehen transfer beendet wird,
 *     bevor scan durchgelaufen ist?
 */

/* TODO ich hab mir ein paar Gedanken zu den ausstehenden Problemen gemacht (Mario):
 * - laufender scan wenn sleep aufgerufen wird:
 *   in currently_scanned_file steht das file, das grade gescannt wird.
 *   dem file können wir sagen, dass wir die ergebnisse von seinem scan nicht wissen wollen.
 *   Und zwar muss die Sync_Directory::dispatchFunction() checken, ob wir noch am Ergebnis interessiert sind.
 *   (Am Ende wird listener->scan_completed_callback(); aufgerufen. Der listener ist das sync objekt, dass sich
 *   da beim scan(shared_from_this()) anmeldet. Man könnte sich im sleep abmelden und am Anfang von dispatch
 *   checken, ob überhaupt noch jemand angemeldet ist..)   
 * 
 * - der aktuell laufende transfer sollte unbedingt im scan in einer objektvariablen gehalten werden
 * 
 * - Wenn wir Informationen über die Dateien vom Partner bekommen (Sync::handle_update_from_partner(SyncFileList files))
 *   aber selber unser scan noch nicht fertig ist, sollten diese infos trotzdem als sync files einsortiert werden, ggf.
 *   müssen "nicht-transparente" SyncDirectories angelegt werden und die dispatchfunktion muss lernen damit zu leben.
 *   ---> Der Code ist aber recht »unübersichtlich«™ ;-) ich weiß nicht ob das jemand machen kann, der den bestehenden
 *   Code nicht selbst geschrieben hat...
 */
void Sync::sleep()
{
	sleeping = true;
	update_notification_pending = false;

	scan_queue.clear();
	updated_cache.clear();
    send_cache.clear();
    currently_scanned_file = SyncFilePtr(); // XXX mal schauen ob das so geht
    base_file = SyncDirPtr();

    // XXX Karin Test
	if ( base_file )
		UI_multiplexer::instance().write_warn("base_file existiert noch!");
	else
		UI_multiplexer::instance().write_info("base_file sauber. :-)");
	if ( currently_scanned_file )
		UI_multiplexer::instance().write_warn("currently_scanned_file existiert noch!");
	else
		UI_multiplexer::instance().write_info("currently_scanned_file sauber. :-)");
}

SyncFileList Sync::get_updated_files(int max)
{
    SyncFileList ret(new list<SyncFilePtr>());

    // prepare up to »max« elements to be moved
    list<SyncFilePtr>::iterator end;
    if ( max == -1 or updated_cache.size() <= max )
    {
        end = updated_cache.end();
        update_notification_pending = false;
    }
    else
    {
        end = updated_cache.begin();
        for ( int i = 0; i < max; i++ )
          end++;
    }


    // move elements from cache into return-list
    ret->splice(ret->end(), updated_cache, updated_cache.begin(), end);

    return ret;
}



void Sync::scan_completed_callback()
{
    // TODO check if we have been canceled!
    
    
    
    cout << "Scan completed: " << currently_scanned_file->get_whole_path() << endl; // XXX debug

    // handle raw pointer of a shared pointer (necessary for dynamic_cast).
    // Make sure to NULL the pointer before overwrite the shared_ptr.
    Sync_Directory* cur_dir = dynamic_cast<Sync_Directory*>(currently_scanned_file.get());  // FIXME use dynamic_pointer_cast
    
    // XXX Example for dynamic_pointer_cast from Ariba Code:
//    shared_ptr<tcpip_endpoint> tcpip_endp =
//            boost::dynamic_pointer_cast<tcpip_endpoint>(endpoint);


    // BRANCH: is dir
    if ( cur_dir != NULL )
    {
        cout << "[DIR]" << endl;  // XXX

        // push subfiles in the new_cache
        BOOST_FOREACH(SyncRegularFilePtr file, cur_dir->get_subfiles())
        {
            /* XXX man könnte sie hier zum scannen reinhängen und erst wenn sich dann
             * als gescannt zurückmelden in den new_cache tun. */
            // scan_queue.push(file);

            // notify partner about this file  (only if outgoing transfers are enabled)
            if ( outgoing_transfers_enabled )
                cache_updated_file(file);
        }
        notify_about_updates();


        // push subdirs in the scan queue
        BOOST_FOREACH(SyncDirPtr dir, cur_dir->get_subdirs())
        {
            scan_queue.push_back(dir);
        }

        // get rid of this ugly pointer.
        cur_dir = NULL;
    }

//    // BRANCH: is file
//    else
//    {
//        if ( ! sending )
//        {
//            // TODO los gehts!  -- nein, nicht hier!!
//        }
//    }


    // continue scanning
    if ( ! scan_queue.empty() )
    {
        currently_scanned_file = scan_queue.front();
        scan_queue.pop_front();

        currently_scanned_file->scan(shared_from_this());
    }
}


// TODO diese Funktion kriegt man auch schöner hin. Am besten in ein paar Unterfunktionen zerteilen..
void Sync::handle_update_from_partner(SyncFileList files)
{
    // for all updated files ...
    while ( ! files->empty() )
    {
        SyncFilePtr new_file = files->front();
        files->pop_front();

        // set our own base path
        new_file->set_base_path(mine_path);


        // XXX find our corresponding sync file object    TODO vgl find_sync_file()
        //   ---->

        // search for corresponding sync file object  TODO Kommentar irgendwie unpassend
        cout << "+++ handle: " << "(" << new_file->get_base_path().string() << "/) " << new_file->get_common_path().string() << endl; //XXX
        boost::filesystem::path path = new_file->get_common_path();

        // FIXME funktioniert nicht wenn share nur eine Datei, oder new_file ein ordner!
        SyncDirPtr cur_dir = boost::static_pointer_cast<Sync_Directory>(base_file);
        cout << " --> cur_dir: " << cur_dir->get_common_path().string() << endl;  // XXX

        // Walk along the path of the current (updated) file,
        // to find (or create) it in our directory hierarchy
        for (boost::filesystem::path::iterator it = path.begin(); it != path.end(); )
        {
            boost::filesystem::path iteration_step = *it;
            it++;
            cout << " - " << iteration_step.string() << endl;  // XXX

            // BRANCH: current step is an intermediate directory
            if ( it != path.end() )
            {
                SyncDirPtr sub_dir = cur_dir->get_subdir(iteration_step);

                // BRANCH: corresponding directory exists in our hierarchy -> use it
                if ( sub_dir )
                {
                    cur_dir = sub_dir;
                }

                // BRANCH: corresponding directory doesn't exist yet in our hierarchy -> create
                else
                {
                    // create Sync_Directory object
                    boost::filesystem::path new_path(cur_dir->get_common_path());
                    new_path /= iteration_step;
                    SyncDirPtr new_dir(new Sync_Directory(new_path)); // FIXME gleiches Base_dir setzen wie cur_dir
                    new_dir->set_transparent(true);
                    new_dir->set_exist_locally(false);

                    // insert the object as subdir
                    cur_dir->add_subdir(new_dir);
                    cur_dir = new_dir;
                }


                cout << "   --> cur_dir: " << cur_dir->get_whole_path().string() << endl;  // XXX

                // intermediate dir not transparent. --> can't handle now. enqueque!
                if ( ! cur_dir->is_transparent() )
                {
                    cout << "   --> NICHT TRANSPARENT!" << endl;  // XXX

                    // TODO aktuell cancel loop, enqueque
                    break;
                }
            }


            // XXX sync file object gefunden (oder zumindest entdeckt, dass wir keins haben)
            //   jetzt muss entschieden werden, wie es weiter geht (das könnte man auch in eingene funktion packen)
            //   (oder in zwei, eine wenn wir das file haben und eine wenn nicht.. oder so..)  [TODO]
            //   ------>

            // * BRANCH: current step is the full path --> so it's an actual file *
            else
            {
                SyncRegularFilePtr new_file_regular = boost::dynamic_pointer_cast<Sync_Regular_File> (new_file);

                // BRANCH: is file (XXX momentan sind "Endpunkte" ja immer echte files)
                if ( new_file_regular )
                {
                    SyncRegularFilePtr corresp_file;
                    
                    // XXX hier ist das irgendwie doof mit den exceptions.. naja, versuchen wir's mal..
                    try
                    {
                        corresp_file = cur_dir->get_subfile(iteration_step);
                    }
                    catch (not_found_exception& e)
                    {
                        UI_multiplexer::instance().write_debug("Creating file object: " + iteration_step.string());
                    }

                    
                    // BRANCH: corresponding file exists
                    if ( corresp_file )
                    {
                        cout << "   --> EXISTING corresponding file: " << "(" << corresp_file->get_base_path().string() << "/) " << corresp_file->get_common_path().string() << endl;

                        // BRANCH: partner requested this file
                        if ( outgoing_transfers_enabled && new_file_regular->get_status() == Sync_File::READY_TO_RECEIVE )
                        {
                            // OK: prepare for sending
                            if ( corresp_file->get_status() == Sync_File::UNDEFINED || corresp_file->get_status() == Sync_File::IN_SYNC )
                            {
                                corresp_file->set_status(Sync_File::READY_TO_SEND);

                                // cache for sending
                                cache_send_file(corresp_file);  // TODO hier wird ggf recht viel losgetreten, prüfen ob das trotzdem klar geht. ggf. könnte dieser aufruf die notification auch in die SystemQueue einhängen...
                                cout << "sending: " << "(" << corresp_file->get_base_path().string() << "/) " << corresp_file->get_common_path().string() << endl;
                            }
                            // ERR: want it ourselves
                            else if ( corresp_file->get_status() == Sync_File::READY_TO_RECEIVE )
                                ;  // TODO und wie lösen wir das Problemchen?
                        }

                        // BRANCH: partner has newer file
                        // TODO rausfinden ob wir das neue lieber mögen und darum bitten.. ;-)  Sync eigenschaften beachten..


                        // TODO CASE: wir wollen dem sein file, s.u.
                    }

                    // BRANCH: corresponding file didn't exist yet --> create file object, demand transfer
                    else
                    {
                        // OK: partner is likely to give us the file
                        if ( incoming_transfers_enabled && new_file_regular->get_status() != Sync_File::READY_TO_RECEIVE )
                        {
                            // mark for receiving
                            new_file->set_exist_locally(false);
                            new_file->set_status(Sync_File::READY_TO_RECEIVE);  // --> we want to have this file
                            cur_dir->add_subfile(new_file_regular);
//                            cout << "   --> CREATE corresponding file: " << new_file->get_whole_path().string() << endl;

                            // register to inform partner about our wish
                            cache_updated_file(new_file_regular);
                        }
                        // ERR: partner wants this file from us, but we don't have it..
                        else
                            ;  // TODO Partner will ein File, dass wir gar nicht haben...
                                // bzw. uns fehlt ein file aber incoming_transfers sind disabled
                    }
                }

                // BRANCH: is directory
                else
                    ; // TODO wie mit file, nur mit Ordner. (Machen wir ja noch nicht..)
            }
        }


    }

    notify_about_updates();
}




void Sync::register_update_listener(SyncUpdatedInterface *updateListener)
{
    update_listener = updateListener;
}

void Sync::cache_updated_file(SyncFilePtr file)
{
    updated_cache.push_back(file);
}



/* notify update listener about new files (if necessary) */
void Sync::notify_about_updates()
{
    if ( !update_notification_pending && !updated_cache.empty() && update_listener )
    {
        update_notification_pending = true;

        update_listener->sync_updated_callback(shared_from_this());
    }
}


void Sync::cache_send_file(SyncRegularFilePtr file)
{
    send_cache.push_back(file);

    if ( ! sending )
    {
        sending = true;
        update_listener->activate_transfers_callback(shared_from_this());
    }
}

// TODO statt dessen die Fkt vom Interface?
void Sync::transfers_ended_notification()
{
    sending = false;
}


/* TransferBundleInterface */
// SENDER, next file
FilePtr Sync::get_next_file()
{
    // BRANCH: have files to send --> give one
    if ( ! send_cache.empty() )
    {
        SyncRegularFilePtr next = send_cache.front();
        send_cache.pop_front();

        FilePtr file(new FileInfo());
        file->set_bundle(shared_from_this());
        path displayname = name / next->get_common_path();
        file->set_filename(displayname.string());
        file->set_path(next->get_whole_path().string());
        file->set_size(next->get_estimatedsize());
        file->set_partner(partner);
        file->set_acceptance(direct_accept);  // direct accept
        
        // update active_cache
        next->set_status(Sync_File::SENDING);  // TODO (so ganz sending ist's ja noch nicht..)
        active_cache.push_back(next);

        return file;
    }

    // BRANCH: no more files to send
    FilePtr empty;
    return empty;
}

// RECEIVER, incoming transfer
shared_ptr<FileInfo> Sync::on_incoming_transfer(shared_ptr<FileInfo> file)
{
    // TODO acceptance vlt erst noch prüfen (oder auch nicht)?
    // TODO file objekt irgendwie auch hier abspeichern?

    try
    {
        SyncFilePtr sync_file = find_sync_file(file);

        cout << "FOUND SYNC FILE ------> " << sync_file->get_whole_path() << " (for " << file->get_filename() << ")" << endl;

        file->set_path(sync_file->get_whole_path().string());
        file->set_acceptance(direct_accept);
        
        // TODO in den active cache aufnehmen?
    }
    catch (not_found_exception& e)
    {
        UI_multiplexer::instance().write_warn("WARN: SYNC FILE NOT FOUND (for " + file->get_filename() + ")");
        file->set_acceptance(false);  // FIXME das müsste eigentlich ein größerer error sein..
    }


    return file;
}



// TODO, hier müssen die ganzen on_ callbacks noch sinnvoll implementiert werden, und ggf. in Filetransfer.cpp überhaupt erst aufgerufen werden...
void Sync::on_transfer_started(shared_ptr<FileInfo> file)
{
}

void Sync::on_transfer_finished(shared_ptr<FileInfo> file)
{
    // TODO Sync finished?
    // Ich würd sagen, hier checken ob noch was im send-cache ist.
    // (Haben wir die aktuell laufen transfers auch irgendwo gecached? Wäre gut, da könnte man dann auch mal reinschauen.)
    // Wenn nein, checken ob noch ein scan läuft.
    // Wenn nein, checken ob alle Dateien gesendet (empfangen?) (Nochmal scan?)
    // Wenn ja, sync beenden. (Muss ggf. automatisch von Scan gemacht werden.)
    //    ---> (Für's erste wollen wir keine Dauersyncs. Kann man später immernoch draufsetzen.)
    
    // ----> Erneuter scan macht nur Sinn, wenn überprüft wird, ob sie Datei inzwischen geändert hat.
    // Das machen wir so oder so noch nicht!
    // Also vielleicht erstmal einfach nur (im Ariba-thread) alle Dateien durchgehen und schauen ob sie gesendet wurden.
    // Wird das momentan überhaupt gespeichert? Wenn nein, sollten wir das hier tun. Dafür wäre wieder der cache der gerade übertragenen Dateien praktisch.
    
    
    // XXX testing.. Ich versuch das jetzt einfach mal..
    string finished_file = file->get_path();
    UI_multiplexer::instance().write_debug("||| " + finished_file + " FINISHED");
    
    // XXX debug output
    UI_multiplexer::instance().write_debug("active_cache:");
    // find finished file in active_cache, update status and delete it from cache
    for ( list<SyncRegularFilePtr>::iterator it=active_cache.begin() ; it != active_cache.end(); it++ )
    {
        // XXX debug output
        UI_multiplexer::instance().write_debug((*it)->get_whole_path().string() + " (" + (*it)->get_status_as_string() + ")");

        
        
        SyncFilePtr f = (*it);
        string cached_file = f->get_whole_path().string();
        
        // TODO muss das ein Stringvergleich über den Pfad sein?
        //   Können wir das nicht irgendwie cooler lösen, oder soll es eben soweit entkoppelt sein?
        if ( finished_file == cached_file )
        {
            f->set_status(Sync_File::IN_SYNC);  // TODO naja, der Transfer ist finished. Reicht uns das für "IN_SYNC"?
            
            active_cache.erase(it);
            break;
        }
    }
    
    // TODO check if sync is finished.
}

void Sync::on_transfer_canceled(shared_ptr<FileInfo> file)
{
    // TODO für's erste wär's glaub ganz gut in dem fall den ganzen sync zu canceln!
    // TODO außerdem sollten wir auch hier den active cache nicht vergessen.
}

void Sync::on_enabled()
{
}

void Sync::on_disabled()
{
    transfers_ended_notification(); // TODO diese funktion soll wohl eher weg, und der Inhalt direkt hier rein
}


/* [TransferBundleInterface] */
SyncFilePtr Sync::find_sync_file(FilePtr file)
{
    // TODO alle couts entfernen

    boost::filesystem::path path = file->get_filename();

    cout << " +++ FILENAME: " << path << endl;

    // TODO was wenn wir auch files syncen können?
    SyncDirPtr cur_sync_dir = boost::static_pointer_cast<Sync_Directory>(base_file);

    for (boost::filesystem::path::iterator it = ++path.begin(); it != path.end(); )   // NOTE: skip first step (filename convention)
    {
        boost::filesystem::path iteration_step = *it;
        it++;  // NOTE: iterate here (not in the loop-head) to easier find the end
        cout << " +++ STEP: " << iteration_step << endl;

        // BRANCH: current step is an intermediate directory
        if ( it != path.end() )
        {
            SyncDirPtr sub_dir = cur_sync_dir->get_subdir(iteration_step);

            cout << " +++ SUBDIR: " << sub_dir->get_whole_path() << endl;

            // OK: corresponding sync-dir exists --> go on
            if ( sub_dir )
            {
                cur_sync_dir = sub_dir;
            }else// ERR: corresponding sync-dir does not exist --> so the sync_file we're looking for can't exist
            {
                // TODO wär hier ne Exception nicht angebrachter? (Dann aber auch im anderen Branch)
                SyncFilePtr empty;
                return empty;
            }
        }
        else// BRANCH: reached the filename
        {
            SyncFilePtr target = cur_sync_dir->get_subfile(iteration_step);

            cout << " +++ TARGET: " << target->get_whole_path() << endl;
            // TODO exception?

            return target;
        }
    }


    // ERR: not found   TODO exception?
    SyncFilePtr empty;
    return empty;
}

boost::filesystem::path Sync::get_name() const
{
    return name;
}
